"""Classes for distributed network model."""

__all__ = ['Network']

#_______________________________________________________________________________

import sys

from _debug import logged
import logging
import random

#_______________________________________________________________________________

_logger = logging.getLogger('nxchaos.network')

#_______________________________________________________________________________

class Worker(object):

    def __init__(self, id_node, id_node_random, graph, algorithm):
        
        self.id = id_node
        self.id_random = id_node_random
        self.__graph = graph
        self.__algorithm = algorithm
        self.inbox = []
        self.outbox = []
        self.__data = None
        self.degree = self.__graph.degree(self.id)

        self.__algorithm.inbox = self.inbox
        self.__algorithm.id = self.id
        self.__algorithm.id_random = self.id_random
        self.__algorithm.send = self.__send
        self.__algorithm.save_data = self.save_data
        self.__algorithm.get_data = self.get_data
        self.__generator = self.__algorithm.run()

    def __send(self, port, value):

        #_logger.debug("send data - id:{id_random}, port:{port}, value:{value}".format(id_random=self.id_random, port=port, value=value))
        self.outbox.append((port, value))

    def _step(self):

        try:
            self.__generator.next()

        except StopIteration:
        # the algorithm finished in this node
            pass

    def is_working(self):

        if self.__generator.gi_frame == None:
            return False

        else:
        # frame object exists
            return True

    def save_data(self, data):

        self.__data = data

    def get_data(self):

        return self.__data


class Network(object):
    
    def __init__(self, graph, algorithm):
        
        self.graph = graph
        self.__algorithm = algorithm
        self.__round_number = 0
        self.partial_data = False
        self.partial_data_path = "partial.txt"

        self.__random_ids_set = self.graph.nodes()
        random.shuffle(self.__random_ids_set)


    def prepare(self):
        
        ids_set = list(self.__random_ids_set)
        self.__round_number = 0

        for node in self.graph:
            self.graph.workers[node] = Worker(node, ids_set.pop(), self.graph, self.__algorithm())

        if self.partial_data:
            f = open(self.partial_data_path, 'w+')
            f.close()

    def round(self):
        self.__round_number += 1
        if not self.is_working():
            raise StopIteration("All nodes finished calculations.")

        for worker in self.graph.workers.values():
            worker._step()

        for worker in self.graph.workers.values():
            while len(worker.inbox) != 0:
                worker.inbox.pop(0)

        for worker in self.graph.workers.values():
            for mail in worker.outbox:
                port, message = mail
                
                if (len(self.graph.neighbors(worker.id)) > 0) \
                    and (port >= 0) \
                    and (port < len(self.graph.neighbors(worker.id))):
                    receiver = self.graph.neighbors(worker.id)[port]
                    port = self.graph.neighbors(receiver).index(worker.id)
                    self.graph.workers[receiver].inbox.append((port, message))
                    _logger.debug("send data - id:{node_id}, id random:{id_random}, receiver:{rec}, value:{value}".format(node_id=worker.id, id_random=worker.id_random, rec=receiver, value=message))
                else:
                    _logger.debug("send data - id:{node_id} port:{port}, value:{value} failed. No such port".format(node_id=worker.id, port=port, value=message))

            while len(worker.outbox) != 0:
                worker.outbox.pop(0)

        if self.partial_data:
            f = open(self.partial_data_path, 'a+')
            f.write("Round: {0}\n".format(self.round_number()))
            for worker in self.graph.workers.values():
                f.write("id: {0}, data: {1}\n".format(worker.id, worker.get_data()))

            f.write("\n")

            f.close()
        

    def is_working(self):

        for worker in self.graph.workers.values():
            if worker.is_working():
                return True

        return False

    def round_number(self):
        return self.__round_number
    
    def work_n_rounds(self, n):

        for i in range(n):
            _logger.debug("Round nr {0}".format(i))
            self.round()

            if not self.is_working():
                break

    def save_partial_data(self, status, path=None):

        self.partial_data = status

        if path is not None:
            self.partial_data_path = path

    def save_data(self, path="calculated_data.txt"):

        f = open(path, "w+")
        for worker in self.graph.workers.values():
            f.write("id: {0}, data: {1}\n".format(worker.id, worker.get_data()))

        f.close()    

    def __getitem__(self, n):

        try:
            return self.graph.workers[n]

        except KeyError:
            try:
                return self.graph.workers[str(n)]

            except KeyError:
                raise IndexError
